import os
import re
import subprocess
import sys
import time
import zipfile
from typing import Optional

import requests
import selenium

from datetime import datetime
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.android.webdriver import WebDriver
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

from lib.http import Ping
from lib.log import Loggers

log = Loggers(name="UI Driver")


class Driver(object):

    def __init__(self, browser, headless=False, *args, **kwargs):
        self.browser = browser
        self.resource = os.path.join(os.path.dirname(__file__), 'resource')
        self.DRIVER_PATH = None
        self.browser_version = None
        self.req_version = None
        self.driver: Optional[WebDriver]= None
        self.headless = headless
        self.__set_browser()
        self.set_driver()

    def set_driver(self):
        assert self.DRIVER_PATH
        if not self.headless:
            self.__set_driver()
        else:
            self.__set_headless_driver()
        self.driver.maximize_window()

    def __set_headless_driver(self):
        if self.browser.upper() == "CHROME":
            chrome_options = webdriver.ChromeOptions()
            chrome_options.add_argument('--headless')
            chrome_options.add_argument('--disable-gpu')
            self.driver = webdriver.Chrome(executable_path=self.DRIVER_PATH, options=chrome_options)
        elif self.browser.upper() == "FIREFOX":
            firefox_options = webdriver.FirefoxOptions()
            firefox_options.add_argument('--headless')
            firefox_options.add_argument('--disable-gpu')
            self.driver = webdriver.Firefox(executable_path=self.DRIVER_PATH, options=firefox_options)
        elif self.browser.upper() == "IE":
            ie_options = webdriver.IeOptions()
            ie_options.add_argument('--headless')
            ie_options.add_argument('--disable-gpu')
            self.driver = webdriver.Ie(options=ie_options, executable_path=self.DRIVER_PATH)


    def __set_driver(self):

        if self.browser.upper() == "CHROME":
            self.driver = webdriver.Chrome(executable_path=self.DRIVER_PATH)
        elif self.browser.upper() == "FIREFOX":
            self.driver = webdriver.Firefox(executable_path=self.DRIVER_PATH)
        elif self.browser.upper() == "IE":
            self.driver = webdriver.Ie(executable_path=self.DRIVER_PATH)

    @staticmethod
    def get_driver_url(browser):
        url = {
            "IE": "https://npm.taobao.org/mirrors/selenium/",
            "CHROME": "https://npm.taobao.org/mirrors/chromedriver/",
            "FIREFOX": "http://npm.taobao.org/mirrors/geckodriver/"
        }
        return url.get(browser.upper())

    @staticmethod
    def get_chrome_browser_reg(platform):
        browser_root = {"Chrome": {"MAC": r"/Applications/Google\ Chrome.app/Contents/MacOS/",
                                   "WINDOWS": r"SOFTWARE\Google\Chrome\BLBeacon",
                                   "Linux": r""
                                   },
                        "Firefox": {
                            "MAC": r"/Applications/Firefox.app/Contents/MacOS/",
                        }}
        return browser_root.get("Chrome").get(platform.upper())

    def __get_browser_version(self, ):
        browser = self.browser.upper()
        platform = self.get_sys_platform()
        cmd_dict = {
            "CHROME": {
                "MAC": r"/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version",
                "WINDOWS": r"SOFTWARE\Google\Chrome\BLBeacon",
                "Linux": r"google-chrome --version"
            },
            "FIREFOX": {
                "MAC": r"/Applications/Firefox.app/Contents/MacOS/firefox --version",
                "LINUX": "firefox --version",
                "WINDOWS": "Pass"
            },
        }
        regs = cmd_dict.get(browser).get(platform.upper())
        cmd = ''
        result = ''
        if platform == "mac":
            result = self.exec_shell(regs)
            self.browser_version = [x.decode("utf-8") for x in result.stdout][0].strip().split(" ")[-1]
            log.warning("您的电脑为 %s 平台, 浏览器为 %s 版本号 %s " % (platform, browser, self.browser_version))
        elif platform == "linux":
            result = self.exec_shell(regs)
            version_message = str([i.decode('utf-8') for i in result.stdout])
            result = re.findall(r'Mozilla Firefox .*', version_message)
            if not result:
                log.error("未获取到浏览器版本，请检查是否安装")
                self.browser_version = "latest"
            self.browser_version = result[0].split(" ")[-1]
        elif platform == "WINDOWS":
            if browser == "IE":
                self.browser_version = selenium.__version__
            elif browser == "CHROME":
                import winreg
                try:
                    key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, regs)
                    self.browser_version = winreg.QueryValueEx(key, "version")[0]
                except Exception:
                    raise Exception("查询注册表chrome版本失败!")
                log.warning("您的电脑为 %s 平台, 浏览器为 %s 版本号 %s " % (platform, browser, self.browser_version))
            elif browser == "FIREFOX":
                """暂无windows设备，后续适配"""
                pass

    @staticmethod
    def exec_shell(cmd):
        result = subprocess.Popen([cmd], stdout=subprocess.PIPE, shell=True)
        return result

    @staticmethod
    def get_sys_platform():
        platform = sys.platform
        if 'darwin' in platform:
            return 'mac'
        elif 'win' in platform:
            return 'windows'
        elif 'linux' in platform:
            return "linux"

    def __set_browser(self):
        self.__get_browser_version()
        file_vr = self.__search_version()
        if not file_vr:
            log.info('当前版本未查询到对应驱动，尝试使用LATEST驱动')
            # raise Exception("未获取到版本号! 请检查!")
        status, file = self.check_driver(file_vr)
        if not status:
            log.warning("未查询到本地驱动")
            self.__generate_driver(file_vr)
            log.info("驱动地址为 %s" % self.DRIVER_PATH)
        else:
            log.warning("系统已存在%sdriver, 无需下载!" % self.browser)
            self.DRIVER_PATH = os.path.join(self.resource, file)
            log.info("驱动地址为 %s" % self.DRIVER_PATH)

    def __generate_driver(self, version):
        if '/' in version:
            version = version.split("/")[0]
        if Ping(self.get_driver_url(self.browser)):
            if version:
                driver = None
                file = None
                r = None
                if self.browser.upper() == "CHROME":
                    if self.get_sys_platform() == "mac":
                        file = "chromedriver_mac64.zip".format(version)
                        driver = "chromedriver"
                    elif "windows" == self.get_sys_platform():
                        file = "chromedriver_win32.zip".format(version)
                        driver = "chromedriver.exe"
                    else:
                        file = "chromedriver_linux64.zip".format(version)
                        driver = "chromedriver"
                    r = requests.get("{}{}/{}".format(self.get_driver_url(self.browser), version, file))
                elif self.browser.upper() == "IE":
                    file = "IEDriverServer_{}.zip".format(version)
                    driver = "IEdriverServer.exe"
                    r = requests.get(
                        "{}{}/IEDriverServer_{}.zip".format(self.get_driver_url(self.browser), self.req_version,
                                                            version))
                elif self.browser.upper() == "FIREFOX":
                    if self.get_sys_platform() == 'windows':
                        bit = ''
                        if 'PROGRAMFILES(X86)' in os.environ:
                            bit = '64'
                        else:
                            bit = '32'
                        file = "geckodriver-{}-win{}.zip".format(version, bit)
                        driver = 'geckodriver.exe'
                    elif self.get_sys_platform() == "linux":
                        cmd = r'uname -a'
                        bit = ''
                        result = self.exec_shell(cmd=cmd)
                        version_message = str([i.decode('utf-8') for i in result.stdout])
                        if "x86_64" in str(version_message):
                            bit = '64'
                        else:
                            bit = '32'
                        file = "geckodriver-{}-linux{}.tar.gz".format(version, bit)
                        driver = 'geckodriver'
                    elif self.get_sys_platform() == 'mac':
                        file = "geckodriver-{}-macos.tar.gz".format(version.split("/")[0])
                        driver = 'geckodriver'
                    r = requests.get('{}{}/{}'.format(self.get_driver_url(self.browser), version, file))
                file_path = os.path.join(self.resource, file)
                log.info("开始下载!")
                with open(file_path, "wb") as f:
                    f.write(r.content)
                self.unzip_driver(file)
                self.change_driver_name(version, driver)

        else:
            raise KeyboardInterrupt("无网络连接，且本地无驱动")

    def check_driver(self, file_vr):
        status, filename = False, None
        if "/" in file_vr:
            file_vr = file_vr.split("/")[0]
        if os.path.exists(self.resource):
            for root, dirs, files in os.walk(self.resource):
                for file in files:
                    if file_vr not in file:
                        try:
                            os.remove(os.path.join(root, file))
                        except Exception:
                            continue
                    else:
                        status, filename = True, file
        else:
            os.mkdir(self.resource)

        return status, filename

    def __search_version(self):
        file_vr = None
        if self.browser.upper() == "CHROME":
            if Ping(self.get_driver_url(self.browser)):
                log.info('当前网络能够连接')
                number = self.browser_version.split(".")[0]
                url = self.get_driver_url(self.browser) + self.browser_version + '/'
                r = requests.get(url)
                bs = BeautifulSoup(r.text, 'lxml')
                record = "{}/{}/notes.txt".format(self.get_driver_url(self.browser), self.browser_version)
                info = requests.get(record)
                text = info.text
                vr = re.findall(r"-+ChromeDriver\s+(\d+\.+\d+\.\d+\.\d+)", text)
                br = re.findall(r'Supports\sChrome\s+version\s+(\d+)', text)
                if number in br and self.browser_version in vr:
                    log.info("找到浏览器对应驱动版本号: {}".format(self.browser_version))
                    file_vr = self.browser_version
                else:
                    log.info('当前版本为最新，未找到驱动，尝试使用当前大版本最新的驱动')
                    latest_url = self.get_driver_url(self.browser)
                    req = requests.get(latest_url)
                    soup = BeautifulSoup(req.text, 'lxml')
                    a_tag = soup.find_all(name='a')
                    like_vr = self.browser_version.split(".")
                    like_vr.pop(-1)
                    like_vr = like_vr[0] + '.' + like_vr[1] + '.' + like_vr[2]
                    like_list = list()
                    for i in a_tag:
                        if i.text.startswith(like_vr):
                            like_list.append(i.text[:-1])
                    file_vr = max(like_list)
            else:
                log.warning('当前网络不通，默认检测本地是否还有驱动!')
                file_vr = self.browser_version
        elif self.browser.upper() == "IE":
            assert self.get_sys_platform() == 'windows'
            if self.browser_version.endswith('0'):
                self.req_version = self.browser_version[:-2]
            if Ping(self.get_driver_url(self.browser)):
                log.info('当前网络可连通')
                url = self.get_driver_url(self.browser) + self.req_version + "/"
                r = requests.get(url)
                bs = BeautifulSoup(r.text, 'lxml')
                url_list = bs.find_all(['a'])
                vr = "Win32_%s" % self.browser_version
                v_l = []
                for i in url_list:
                    v_l.append(i.attrs['href'])
                if vr in str(v_l):
                    log.info("找到浏览器对应驱动版本号: {}".format(file_vr))
                    file_vr = vr
            else:
                log.warning('当前无网络连接')
                file_vr = "Win32_%s" % self.browser_version
        elif self.browser.upper() == "FIREFOX":
            if Ping(self.get_driver_url(self.browser)):
                log.info('当前网络能够连接, firefox浏览器使用最新驱动')
                url = self.get_driver_url(self.browser)
                r = requests.get(url)
                bs = BeautifulSoup(r.text, 'lxml')
                url_list = bs.find_all(['a'])
                publish_date = ''
                legal_tags = dict()
                for i in url_list:
                    if i.attrs.get('href') and "geckodriver" in i.attrs.get('href'):
                        publish_date = str(i.nextSibling).strip()
                        if ' ' in publish_date:
                            publish_date = publish_date.split(" ")[0]
                            UTC_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
                            publish_date = datetime.strptime(publish_date, UTC_FORMAT, )
                            legal_tags[publish_date] = i.text
                max_date = publish_date
                for j in legal_tags:
                    if j > max_date:
                        max_date = j
                    else:
                        continue
                file_vr = legal_tags[max_date]
        return file_vr

    def unzip_driver(self, filename):
        if self.get_sys_platform() == "mac" or self.get_sys_platform() == 'linux':
            if self.browser.upper() != "FIREFOX":
                os.system('cd {};unzip {}'.format(self.resource, filename))
                os.path.join(self.resource, filename)
            else:
                os.system('cd {};tar -zxvf {}'.format(self.resource, filename))
        elif self.get_sys_platform() == "windows":
            self.unzip_win(os.path.join(self.resource, filename))
            os.remove(os.path.join(self.resource, filename))

    def change_driver_name(self, version, filename):
        new_file = ''
        if self.get_sys_platform() == "mac" or self.get_sys_platform() == "linux":
            new_file = "{}_{}".format(filename, version)
            if new_file.endswith("/"):
                new_file = new_file.split("/")[0]
        elif self.get_sys_platform() == "windows":
            L = filename.split(".")
            new_file = "{}_{}.{}".format("".join(L[:-1]), version, L[-1])
        os.rename(os.path.join(self.resource, filename),
                  os.path.join(self.resource, new_file))
        self.DRIVER_PATH = os.path.join(self.resource, new_file)

    def unzip_win(self, filename):
        with zipfile.ZipFile(filename) as f:
            for names in f.namelist():
                f.extract(names, self.resource)


LOCATE_MODE = {
    'css': By.CSS_SELECTOR,
    'xpath': By.XPATH,
    'name': By.NAME,
    'id': By.ID,
    'class': By.CLASS_NAME,
}


class BasePage(object):

    # locator: ['css', '.content']
    #          ('id',  '#test)
    #           ('class', 'content')
    #           ('name', 'group_id')
    #           ("xpath", "/html/body/header/div/div/div[2]/ul/li[2]/div/form/input[2]")
    #
    #
    def __init__(self, driver: webdriver.Chrome or webdriver.Firefox or webdriver.Ie, time_out=5):
        if isinstance(driver, webdriver.Chrome) or isinstance(driver, webdriver.Firefox) or isinstance(driver,
                                                                                                       webdriver.Ie):
            self.driver = driver
        else:
            return
        self.timeout = time_out
        self.actions = webdriver.ActionChains(self.driver)
        self.wait = WebDriverWait(self.driver, self.timeout)

    def get_url(self, url):
        """打开网址并验证"""
        self.driver.maximize_window()
        self.driver.set_page_load_timeout(60)
        try:
            self.driver.get(url)
            self.driver.implicitly_wait(10)
            log.info("打开网页：%s" % url)
        except TimeoutException:
            raise TimeoutException("打开%s超时请检查网络或网址服务器" % url)

    @staticmethod
    def element_locator(func, locator):
        """元素定位器"""
        name, value = locator
        return func(LOCATE_MODE[name], value)

    def find_element(self, locator) -> WebElement:
        """寻找单个元素
        locator: (using_method, method_correspond_expressions)
        :return single WebElement
        """
        return self.element_locator(lambda *args: self.wait.until(
            EC.presence_of_element_located(args)), locator)

    def find_elements(self, locator) -> list:
        """查找多个相同的元素
        locator: (using_method, method_correspond_expressions)
        :return list of WebElement
        """

        return self.element_locator(lambda *args: self.wait.until(
            EC.presence_of_all_elements_located(args)), locator)

    def elements_num(self, locator) -> int:
        """获取相同元素的个数
        locator: (using_method, method_correspond_expressions)
        :return length of WebElements
        """
        number = len(self.find_elements(locator))
        log.info("相同元素：{}".format((locator, number)))
        return number

    def switch_to_frame(self, locator):
        """
        :param locator:(using_method, method_correspond_expressions)
        :return: None Swtich to iframe
        """
        time.sleep(0.5)
        ele = self.find_element(locator)
        log.info("切换至定位元素为%s%s的ifraeme" % locator)
        self.driver.switch_to.frame(ele)

    def input_text(self, locator, txt):
        """输入(输入前先清空)"""
        time.sleep(0.5)
        ele = self.find_element(locator)
        ele.clear()
        ele.send_keys(txt)
        log.info("输入文本：{}".format(txt))

    def click(self, locator):
        """点击
        :param locator:(using_method, method_correspond_expressions)
        :return: None"""
        self.find_element(locator).click()
        time.sleep(1)
        log.info("点击元素：{}".format(locator))

    def element_text(self, locator) -> str:
        """获取当前的text
        :param locator:(using_method, method_correspond_expressions)
        :return: WebElements contains text
        """
        _text = self.find_element(locator).text
        log.info("获取文本：{}".format(_text))
        return _text

    def get_source(self) -> str:
        """获取页面源代码
        :param locator:(using_method, method_correspond_expressions)
        :return: source of current_page
        """
        return self.driver.page_source

    def refresh(self):
        """刷新页面F5
        refresh current_page
        """
        self.driver.refresh()
        self.driver.implicitly_wait(30)

    def url(self) -> str:
        return self.driver.current_url

    def title(self) -> str:
        if isinstance(self.driver, selenium.webdriver.Chrome):
            return self.driver.title

    def save_screenshot(self, filename) -> bool:
        """
        save current page to a file
        :param filename: save to filename
        :return: bool True or False
        """
        try:
            self.driver.get_screenshot_as_file(filename)
            log.info("保存截图至%s" % filename)
            return True
        except:
            return False

    def execute_script(self, javascript):
        """
        execute Javascript in console(F12)
        :param javascript: Javascript Expressions
        :return:
        """
        return self.driver.execute_script(javascript)

    def switch_to_window(self, window_name):

        return self.driver.switch_to.window(window_name=window_name)

    def close_window(self, ):
        """close current window"""
        return self.driver.close()

    def quit(self):
        """quit driver"""
        return self.driver.quit()

    def double_click(self, element: WebElement):
        """
        :param element: WebElement Which Has Find
        :return:
        """
        return self.actions.double_click(element)

    def move_by_offset(self, postion: tuple):
        """
        mouse move to you given postion

        :param postion: x_offset and y_offset
        :return:
        """
        return self.actions.move_by_offset(*postion)

    def move_to_element(self, element: WebElement):
        """
        move mouse to given element
        :param element:
        :return:
        """
        return self.actions.move_to_element(element)

    def click_use_mouse_right_button(self, element: WebElement):
        """
        right click given element
        :param element:
        :return:
        """
        return self.actions.context_click(element)


